package org.jeecg.common.util.changeLog;

import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;
import io.swagger.annotations.ApiModelProperty;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Slf4j
public class BeanChangeUtil<T> {

    public static <T> String getChangeInfo(T oldBean, T newBean) {
        BeanChangeUtil<T> t = new BeanChangeUtil<>();
        ChangePropertyMsg cfs = t.contrastObj(oldBean, newBean);
        if (Objects.nonNull(cfs) && Objects.nonNull(cfs.getChangeMsg())) {
            return cfs.getChangeMsg();
        }
        return null;
    }

    public ChangePropertyMsg contrastObj(Object oldBean, Object newBean) {
        T oldPojo = (T) oldBean;
        Field[] fields = oldPojo.getClass().getDeclaredFields();
        return jdk8OrAfter(Arrays.asList(fields), oldPojo, (T) newBean, new HashSet<>(), 0);
    }

    private ChangePropertyMsg jdk8OrAfter(List<Field> fields, T oldBean, T newBean, Set<Object> visitedObjects, int depth) {
        ChangePropertyMsg cf = new ChangePropertyMsg();
        StringBuilder str = new StringBuilder();
        List<String> fieldList = new ArrayList<>();
        Stack<Field> fieldStack = new Stack<>();
        fieldStack.addAll(fields);
        while (!fieldStack.isEmpty()) {
            Field field = fieldStack.pop();
            field.setAccessible(true);
            if (field.isAnnotationPresent(ApiModelProperty.class)) {
                try {
                    Object newValue = field.get(newBean);
                    Object oldValue = field.get(oldBean);
                    if (!Objects.equals(newValue, oldValue)) {
                        boolean isOldValueArray = ArrayUtil.isArray(oldValue);
                        boolean isNewValueArray = ArrayUtil.isArray(newValue);
                        if (isOldValueArray && isNewValueArray) {
                            Object[] oldArray = (Object[]) oldValue;
                            Object[] newArray = (Object[]) newValue;
                            if (!Arrays.deepEquals(oldArray, newArray)) {
                                fieldList.add(field.getName());
                                str.append(field.getAnnotation(ApiModelProperty.class).value() + "：");
                                str.append("修改前=【" + Arrays.toString(oldArray) + "】，修改后=【" + Arrays.toString(newArray) + "】;\n");
                                cf.addChange(field.getAnnotation(ApiModelProperty.class).value(),
                                        Arrays.toString(oldArray),
                                        Arrays.toString(newArray), "");
                            }
                        } else {
                            fieldList.add(field.getName());
                            str.append(field.getAnnotation(ApiModelProperty.class).value() + "：");
                            str.append("修改前=【" + formatPropertyValue(field, oldValue, visitedObjects, depth + 1) + "】，修改后=【" + formatPropertyValue(field, newValue, visitedObjects, depth + 1) + "】;\n");
                            cf.addChange(field.getAnnotation(ApiModelProperty.class).value(),
                                    formatPropertyValue(field, oldValue, visitedObjects, depth + 1),
                                    formatPropertyValue(field, newValue, visitedObjects, depth + 1), "");
                        }
                    }
                } catch (Exception e) {
                    log.error("比对Bean属性是否变化失败，", e);
                }
            }
        }
        cf.setChangeMsg(str.toString());
        cf.setProperties(fieldList);
        return cf;
    }

    private static String formatPropertyValue(Field field, Object value, Set<Object> visitedObjects, int depth) {
        if (depth > 10) { // Maximum recursion depth
            return "Max recursion depth reached";
        }

        if (value == null) {
            return "null";
        } else if (value instanceof LocalDateTime) {
            return ((LocalDateTime) value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        } else if (value.getClass().isArray()) {
            // Handle array
            if (value instanceof char[]) {
                return new String((char[]) value);
            } else if (value.getClass().getComponentType().isPrimitive()) {
                return Arrays.toString((Object[]) value);
            } else {
                if (visitedObjects.contains(value)) {
                    return "Circular reference";
                }
                visitedObjects.add(value);

                StringBuilder result = new StringBuilder();
                for (Object item : (Object[]) value) {
                    result.append(formatPropertyValue(field, item, visitedObjects, depth + 1)).append(", ");
                }
                if (result.length() > 2) {
                    result.delete(result.length() - 2, result.length());
                }
                return result.toString();
            }
        } else if (value instanceof List) {
            // Handle List
            if (visitedObjects.contains(value)) {
                return "Circular reference";
            }
            visitedObjects.add(value);

            StringBuilder result = new StringBuilder();
            for (Object item : (List<?>) value) {
                result.append(formatPropertyValue(field, item, visitedObjects, depth + 1)).append(", ");
            }
            if (result.length() > 2) {
                result.delete(result.length() - 2, result.length());
            }
            return result.toString();
        } else if (value instanceof Map) {
            // Handle Map
            if (visitedObjects.contains(value)) {
                return "Circular reference";
            }
            visitedObjects.add(value);

            StringBuilder result = new StringBuilder();
            for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
                result.append(entry.getKey()).append("=").append(formatPropertyValue(field, entry.getValue(), visitedObjects, depth + 1)).append(", ");
            }
            if (result.length() > 2) {
                result.delete(result.length() - 2, result.length());
            }
            return result.toString();
        } else if (value instanceof String) {
            return (String) value;
        } else {
            // Handle other objects
            if (visitedObjects.contains(value)) {
                return "Circular reference";
            }
            visitedObjects.add(value);

            StringBuilder result = new StringBuilder();
            List<Field> fields = getAllFields(value.getClass());
            for (Field innerField : fields) {
                innerField.setAccessible(true);
                try {
                    Object fieldValue = innerField.get(value);
                    if (innerField.isAnnotationPresent(ApiModelProperty.class)) {
                        result.append(innerField.getAnnotation(ApiModelProperty.class).value()).append("=");
                    } else {
                        result.append(innerField.getName()).append("=");
                    }
                    result.append(formatPropertyValue(innerField, fieldValue, visitedObjects, depth + 1)).append(", ");
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            if (result.length() > 2) {
                result.delete(result.length() - 2, result.length());
            }
            return result.toString();
        }
    }

    private static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        while (clazz != null) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

    public static void main(String[] args) {
        OtherObject object = new OtherObject(2, "李四");
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("1");
        list1.add("2");
        Entity entity = new Entity(1, "张三", 1l, new Date(), list1, object);
        OtherObject object1 = new OtherObject(3, "李四2");
        ArrayList<String> list2 = new ArrayList<>();
        list2.add("1");
        Entity entity1 = new Entity(1, "李四", 1l, new Date(), list2, object1);
        BeanChangeUtil<Object> objectBeanChangeUtil = new BeanChangeUtil<>();
        ChangePropertyMsg changePropertyMsg = objectBeanChangeUtil.contrastObj(entity1, entity);
        String changeMsg = changePropertyMsg.getChangeMsg();
        System.out.println("changeMsg = " + changeMsg);

    }
}
